/*
 *	MIDAS Context Based Routing
 *	cbr_crt.cc
 *	Copyright (c) 2007 Bartlomiej Zolnierkiewicz
 *	MEAG IT PW
 *
 *	Network interfaces are serviced in serial (same is true for CBR packet
 *	send / receive handlers) so no special locking is needed currently.
 */

#include <iostream>
#include <sstream>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <semaphore.h>

#include "scheduler.h"	// for cbr.h
#include "packet.h"	// for cbr.h
#include "serializer.h"

#include "cbr_types.h"
#include "cbr.h"
#include "cbr_msgcache.h"

using namespace std;

#define MAXDATASIZE 65535

/* port used for CBR->CRT TCP communication */
static int crt_port = -1;

/* ip address used for CBR->CRT TCP communication */
string ip;

/* port used for CRT->CBR TCP communication */
static int crt_port2 = -1;

/**
 * Connects to CRT component.
 *
 * @param port CBR Java server port number
 *
 * Connects CBR (client) to CBR Java server (part of CRT component)
 * using communication over local TCP socket and the port @a port.
 *
 * @return socket descriptor on success, -1 otherwise.
 */

int __cbr_connect_to_crt(unsigned short port, const char* ipaddress)
{
	int s;
	struct sockaddr_in dst_addr;

	if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		int errval = errno;
		char *errstr = strerror(errno);

		cbr_log("socket() error: %d (%s)\n", errval, errstr);
		return -1;
	}

	memset(&dst_addr, 0, sizeof(dst_addr));
	dst_addr.sin_family = AF_INET;
	dst_addr.sin_port = htons(port);
	dst_addr.sin_addr.s_addr = inet_addr(ipaddress);

	if (connect(s, (struct sockaddr *)&dst_addr, sizeof(dst_addr)) == -1) {
		int errval = errno;
		char *errstr = strerror(errno);

		cbr_log("connect() error: %d (%s)\n", errval, errstr);
		return -1;
	}

	/* we have a valid socket decriptor number */
	cbr_debug("using socket=%d for connection\n", s);

	return s;
}

/**
 * Setups data for CBR->CRT communication.
 *
 * @param port CBR Java server port number
 *
 * Setups 'crt_port' static variable.
 *
 * @return 1.
 */

int cbr_connect_to_crt(unsigned short port, string &address)
{
	crt_port = port;

	ip = address;
	
	return 1;
}

/**
 * Setups data for CRT->CBR communication.
 *
 * @param port CBR Java server port number
 *
 * Setups 'crt_port2' static variable.
 *
 * @return 1.
 */

int cbr_connect_to_crt2(unsigned short port, string &address)
{
	crt_port2 = port;
	
	ip = address;

	return 1;
}

/**
 * Sends bytes to socket.
 *
 * @param port number of TCP port used for communication with CRT
 * @param buf bytes to send
 * @param size number of bytes to send
 *
 * Sends @a size bytes from the buffer @a buf to the socket descriptor @a s.
 *
 * @return socket descriptor on success and -1 on failure.
 */

static int cbr_sendall(int port, const char* ip, const char *buf, int size)
{
	int s;
	int total = 0;
	int left = size;
	int n = 0;

	if (cbr_log_functions())
		cbr_log("in %s\n", __func__);

	s = __cbr_connect_to_crt(port, ip);
	if (s < 0) {
		cbr_log("error: can't connect to CRT\n");
		return -1;
	}

	while (total < size) {
		n = send(s, buf + total, left, 0);
		if (n == -1) {
			int errval = errno;
			char *errstr = strerror(errno);

			cbr_log("send() error: %d (%s)\n", errval, errstr);
			break;
		}
		total += n;
		left -= n;
	}

	cbr_debug(" %s: %d byte(s) sent\n", __FUNCTION__, total);

	if (n == -1) {
		cbr_debug("%s failed\n", __func__);
		close(s);
		s = -1;
	}

	return s;
}

/**
 * Receive bytes from socket.
 *
 * @param s socket descriptor
 * @param buf receive buffer
 * @param size number of bytes to receive
 *
 * Receives @a size bytes to the buffer @a buf from the socket descriptor @a s.
 *
 * @return 0 on success and -1 on failure.
 */

static int cbr_recvall(int s, char *buf, int size)
{
	int total = size;
	int done = 0;
	int n = 0;

	if (cbr_log_functions())
		cbr_log("in %s()\n", __func__);

	while (done < size) {
		n = recv(s, buf + done, total, 0);
		// 0 when the peer has performed an orderly shutdown
		if (n == -1 || n == 0) {
			if (n == 0 || (n == -1 && errno == EAGAIN))
				continue;

			int errval = errno;
			char *errstr = strerror(errno);

			cbr_log("recv() error: %d (%s)\n", errval, errstr);
			break;
//			continue;
		}
		total -= n;
		done += n;
	}

	cbr_debug(" %s: %d byte(s) received\n", __FUNCTION__, done);

	return (n == -1) ? -1 : 0;
}

/**
 * Receives bytes from socket.
 *
 * @param s socket descriptor
 * @param buf receive buffer
 *
 * Receives data from CRT to the buffer @a buf from the socket descriptor @a s.
 *
 * First it tries to read header (13 bytes).  Then if the header is
 * valid (protocol version is supported) and the request was successful
 * (CRT returned good status) it tries to read "length" field (2 bytes)
 * and the data itself (number of bytes is given by the "length" field).
 *
 * @return number of bytes received on success and -1 on failure.
 */

static int cbr_recv(int s, char *buf)
{
	char *header = new char[14];
	char *length = new char[4];
	int len, rc = -1;

	if (cbr_log_functions())
		cbr_log("in %s\n", __func__);

	// read the header first
	if (cbr_recvall(s, header, 13) == -1)
		goto out;

	header[13] = '\0';
	cbr_debug(" header received: %s", header);

	if (strncmp(header, "CBR/0.1 ", 8)) {
		cbr_log(" error: bad protocol\n");
		goto out;
	}

	if (strncmp(header + 8, "202", 3)) {
		cbr_log(" error: bad status\n");
		goto out;
	}

	// read the length (two bytes of length + "\r\n")
	if (cbr_recvall(s, length, 4) == -1)
		goto out;

	// convert to integer
	len = ntohs(*(length + 1) << 8 | *length);

	cbr_debug(" data length: %d\n", len);

	if (len == 0) {
		rc = 0;
		goto out;
	}

	// do the real work
	if (cbr_recvall(s, buf, len) == -1)
		goto out;

	buf[len] = '\0';
	cbr_debug(" data: %s\n", buf);

	rc = len;
out:
	close(s);

	delete[] length;
	delete[] header;

	return rc;
}

// CBR to CRT requests
#define CRTREQ_GETSUPERCLASSES			"GETSUPERCLASSES"
#define CRTREQ_GETSUBCLASSES			"GETSUBCLASSES"
#define CRTREQ_GETCOMMONSUPERCLASSES		"GETCOMMONSUPERCLASSES"
#define CRTREQ_GETCLOSESTCOMMONSUPERCLASS	"GETCLOSESTCOMMONSUPERCLASS"
#define CRTREQ_GETMSG				"GETMSG"
#define CRTREQ_PUTMSG				"PUTMSG"
#define CRTREQ_CHECKCBA				"CHECKCBA"
#define CRTREQ_GETCTXDESC			"GETCTXDESC"
#define CRTREQ_GETNODEIP			"GETNODEIP"
#define CRTREQ_GETELEMENTARYCONCEPTS		"GETELEMENTARYCONCEPTS"
#define CRTREQ_GETSUPERCONCEPTS			"GETSUPERCONCEPTS"
#define CRTREQ_GETSUBCONCEPTS			"GETSUBCONCEPTS"

/**
 * Dumps content of cbr_ConceptsSet.
 *
 * @param set set of concepts
 */

void cbr_ConceptsSet_dump(cbr_ConceptsSet *set)
{
	cbr_ConceptsSet::iterator i;

	cbr_debug("dumping content of cbr_ConceptsSet=%x\n", set);

	for (i = set->begin(); i != set->end(); i++)
		cbr_debug("%s\n", (*i).c_str());
}

/**
 * Converts message body of CRT server reply to a concept set.
 *
 * @param msg message body
 * @param set reference to concept set
 *
 * Used for processing replies to:
 *	CRTREQ_GETSUPERCLASSES
 *	CRTREQ_GETSUBCLASSES
 *	CRTREQ_GETCOMMONSUPERCLASSES
 *	CRTREQ_GETCLOSESTCOMMONSUPERCLASS
 * requests.
 */

static void replyToClasses(char *msg, cbr_ConceptsSet &set)
{
	istringstream converter;
	int nr_classes;
	string str;

	// clear our new set of concepts?
//	set->clear();

	converter.str(msg);
	converter >> nr_classes;

	for (int i = 0; i < nr_classes; i++) {
		converter >> str;
		// add concept to the set
		set.insert(str);
	}
}

/**
 * Sends request CRT server to obtain list of class(es).
 *
 * @param req request string
 * @param set reference to concept set
 *
 * Sends request CRT server to obtain list of class(es), used by:
 *	CRTREQ_GETSUPERCLASSES
 *	CRTREQ_GETSUBCLASSES
 *	CRTREQ_GETCOMMONSUPERCLASSES
 *	CRTREQ_GETCLOSESTCOMMONSUPERCLASS
 * requests.
 */

static void getClasses(string &req, cbr_ConceptsSet &set)
{
	int s, len;
	char *tmp = new char[MAXDATASIZE];

	cbr_debug("%s", req.c_str());
	len = strlen(req.c_str());

	// send request
	s = cbr_sendall(crt_port, ip.c_str(), req.c_str(), len);
	if (s < 0)
		goto out;

	cbr_debug(" wait for reply\n");

	// receive reply
	len = cbr_recv(s, tmp);
	if (len == -1)
		goto out;

	if (len == 0)
		goto out;

	// process reply
	replyToClasses(tmp, set);
out:
	delete[] tmp;
}

/**
 * Sends request for super classes of the given concept.
 *
 * @param concept concept
 * @param set concept set
 *
 * Sends CRTREQ_GETSUPERCLASSES request to CRT server to obtain super
 * classes of a given concept.
 */

void cbr_getSuperClasses(const cbr_Concept &concept, cbr_ConceptsSet &set)
{
	string req;

	if (concept.empty())
		return;

	// prepare request
	req.append(CRTREQ_GETSUPERCLASSES);
	req.append(" ");
	req.append(concept);
	req.append("\r\n");

	getClasses(req, set);
}

/**
 * Sends request for sub classes of the given concept.
 *
 * @param concept concept
 * @param set concept set
 *
 * Sends CRTREQ_GETSUBCLASSES request to CRT server to obtain sub
 * classes of a given concept.
 */

void cbr_getSubClasses(const cbr_Concept &concept, cbr_ConceptsSet &set)
{
	string req;

	// prepare request
	req.append(CRTREQ_GETSUBCLASSES);
	req.append(" ");
	req.append(concept);
	req.append("\r\n");

	getClasses(req, set);
}

/**
 * Sends request for the common super classes of two concepts.
 *
 * @param concept1 first concept
 * @param concept2 second concept
 * @param set concept set
 *
 * Sends CRTREQ_GETCOMMONSUPERCLASSES request to CRT server to obtain
 * the common super classes of the two concepts.
 */

void cbr_getCommonSuperClasses(const cbr_Concept &concept1,
			       const cbr_Concept &concept2,
			       cbr_ConceptsSet &set)
{
	string req;

	req.append(CRTREQ_GETCOMMONSUPERCLASSES);
	req.append(" ");
	req.append(concept1);
	req.append(" ");
	req.append(concept2);
	req.append("\r\n");

	getClasses(req, set);
}

/**
 * Sends request for the closes common super class of two concepts.
 *
 * @param concept1 first concept
 * @param concept2 second concept
 *
 * Sends CRTREQ_GETCLOSESTCOMMONSUPERCLASSES request to CRT server to obtain
 * the closest common super concept of the two concepts.
 *
 * Returns the closest common super concept of the given concepts
 * (or the concept "Thing" if none was found).
 */

cbr_Concept cbr_getClosestCommonSuperConcept(const cbr_Concept &concept1,
					     const cbr_Concept &concept2)
{
	cbr_ConceptsSet set;
	string req;

	set.clear();

	req.append(CRTREQ_GETCLOSESTCOMMONSUPERCLASS);
	req.append(" ");
	req.append(concept1);
	req.append(" ");
	req.append(concept2);
	req.append("\r\n");

	getClasses(req, set);

	if (set.empty())
		return cbr_Concept("Thing");

//	cbr_ConceptsSet::const_iterator i = set.begin();
//	return cbr_Concept(*(set.begin()));
	return *(set.begin());
}

/**
 * Sends request containing new received message.
 *
 * @param msg message content
 * @param id CBR node id
 * @param size size of the message
 *
 * Sends CRTREQ_PUTMSG request to CRT server containing the
 * new received message.
 *
 * @return 0 on success and -1 on failure.
 */

int cbr_PutMsg(void *msg, int id, int size)
{
	char *tmp, reply[100];
	string req;
	// integer to string
	stringstream id_oss, oss;
	id_oss << id;
	string id_str(id_oss.str());
	oss << size;
	string size_str(oss.str());
	int s, len, rc = -1;

	req.append(CRTREQ_PUTMSG);
	req.append(" ");
	req.append(id_str);
	req.append(" ");
	req.append(size_str);
	req.append("\r\n");

	cbr_debug("%s -> %d: size: %d\n", req.c_str(), id, req.size());

	// CRTREQ_PUTMSG is a bit special cause we send "raw" data
	len = req.size() + size + 2;
	tmp = new char[len];
	// copy the header
	memcpy(tmp, req.c_str(), req.size());
	// copy the message body
	memcpy(tmp + req.size(), msg, size);
	// put the final "\r\n"
	tmp[len - 2] = '\r';
	tmp[len - 1] = '\n';

	cbr_debug(" %d\n", len);

	int xlen = 0;
	while (xlen < len)
		xlen++;

	s = cbr_sendall(crt_port, ip.c_str(), tmp, len);
	if (s < 0)
		goto out;

	cbr_debug(" wait for reply\n");

	// receive reply
	len = cbr_recv(s, reply);
	if (len == -1)
		goto out;

	rc = 0;
out:
	delete[] tmp;

	return rc;
}

/**
 * Sends request to obtain new message to send.
 *
 * Sends CRTREQ_GETMSG request to CRT server to obtain new
 * message to send.
 *
 * @return 0 on success and -1 on failure.
 */

int cbr_GetMsg(CBR *agent)
{
	string req;
	char *tmp = new char[MAXDATASIZE];
	char *msg;
	int s, len, rc = -1;

	req.append(CRTREQ_GETMSG);
	req.append("\r\n");

	cbr_debug("%s", req.c_str());

	s = cbr_sendall(crt_port2, ip.c_str(), req.c_str(), strlen(req.c_str()));
	if (s < 0)
		goto out;

	cbr_debug(" wait for reply\n");

	// receive reply
	len = cbr_recv(s, tmp);
	if (len == -1)
		goto out;

	if (len == 0) {
		cbr_debug(" no new messages\n");
		rc = 0;
		goto out;
	}

	char *addr, *flags_str;
	int addr_len, flags_len, flags, mlen;

	// the format is: "<address>\r\n<flags>\r\n<message>\r\n"
	addr_len = strstr(tmp, "\n") - tmp + 1; // cannot use '\n' in address
	cbr_debug(" addr_len: %d\n", addr_len);
	addr = new char[addr_len];
	memcpy(addr, tmp, addr_len - 2); // -2 => fixup for "'\r\n"
	addr[addr_len - 2] = '\0';

	flags_len = strstr(tmp + addr_len, "\n") - addr_len - tmp + 1;
	cbr_debug(" flags_len: %d\n", flags_len);
	flags_str = new char[flags_len];
	memcpy(flags_str, tmp + addr_len, flags_len - 2);
	flags = atoi(flags_str);

	mlen = len - addr_len - flags_len - 1;
	cbr_debug(" len: %d\n mlen: %d\n", len, mlen - 1);

	if (mlen - 1 == 0) {
		rc = 0;
		goto out_success;
	}

	msg = new char[mlen];
	memcpy(msg, tmp + addr_len + flags_len, mlen - 1);
	msg[mlen - 1] = '\0';

	cbr_debug(" queueing:\n  addr: %s\n  flags: %d\n  size: %d\n",
		  addr, flags, mlen - 1);

	// "-2" == CBR packet to be processed by local node
	if (!strcmp(addr, "-2")) {
		agent->cbr_recv(msg);
		goto out_packet;
	}

	u_int32_t trID;
	cbr_ContextAddress *maddr;
	cbr_Msg *cbrmsg;

	trID = agent->getTransactionID();
	maddr = new cbr_ContextAddress(addr);
	cbrmsg = new cbr_Msg(mlen - 1, mlen - 1, msg, maddr,
			     trID, cbr_Msg::RREQ, OUTGOING_MSG_LIFE);

	agent->sendDataMessage(cbrmsg, 0);

	rc = 0;

	delete maddr;
out_packet:
	delete[] msg;
out_success:
	delete[] flags_str;
	delete[] addr;
out:
	delete[] tmp;

	return rc;
}

/**
 * Check Context Based Address.
 *
 * @param cba Context Based Address string
 * @param check check result
 *
 * @returns -1 if CBA cannot be checked, 0 otherwise.
 */

int cbr_checkCBA(const char *cba, bool &check)
{
	string req, str;
	char *tmp = new char[MAXDATASIZE];
	int s, len, rc = -1;
	istringstream converter;

	check = 0;

	if (cba == NULL || *cba == '\0') {
		rc = 0;
		goto out;
	}

	req.append(CRTREQ_CHECKCBA);
	req.append(" ");
	req.append(cba);
	req.append("\r\n");

	cbr_debug("%s", req.c_str());

	s = cbr_sendall(crt_port, ip.c_str(), req.c_str(), strlen(req.c_str()));
	if (s < 0)
		goto out;

	cbr_debug(" wait for reply\n");

	// receive reply
	len = cbr_recv(s, tmp);
	if (len == -1)
		goto out;

	// len can't be zero

	// the format is: "<check_value>\r\n"
	converter.str(tmp);
	converter >> str;

	if (str.compare("true") == 0)
		check = 1;
	else if (str.compare("false") == 0)
		check = 0;
	else
		goto out;

	cbr_debug(" check_value: %d\n", check);

	rc = 0;
out:
	delete[] tmp;

	return rc;
}

/**
 * Gets Context description.
 *
 * @param id local CBR id
 * @param ctx string representing context
 *
 * @returns 0 on success, -1 otherwise.
 */

int cbr_getCtxDesc(cbr_ID id, string &ctx)
{
	string req;
	// integer to string
	stringstream oss;
	oss << id;
	string id_str(oss.str());
	char *tmp = new char[MAXDATASIZE];
	int s, len, rc = -1;
	istringstream converter;

	req.append(CRTREQ_GETCTXDESC);
	req.append(" ");
	req.append(id_str);
	req.append("\r\n");

	cbr_debug("%s", req.c_str());

	s = cbr_sendall(crt_port, ip.c_str(), req.c_str(), strlen(req.c_str()));
	if (s < 0)
		goto out;

	cbr_debug(" wait for reply\n");

	// receive reply
	len = cbr_recv(s, tmp);
	if (len == -1)
		goto out;

	if (len == 0) {
		rc = 0;
		goto out;
	}

	// the format is: "<check_value>\r\n"
	converter.str(tmp);
	converter >> ctx;

	cbr_debug(" ctx: %s\n", ctx.c_str());

	rc = 0;
out:
	delete[] tmp;

	return rc;
}

/**
 * Gets node's IP address.
 *
 * @param id CBR id
 * @param ip pointer to buffer to store IP address
 *
 * @returns 0 on success, -1 otherwise.
 */

int cbr_getNodeIP(cbr_ID id, string &ip)
{
	string req;
	// integer to string
	stringstream oss;
	oss << id;
	string id_str(oss.str());
	char *tmp = new char[MAXDATASIZE];
	int s, len, rc = -1;
	istringstream converter;

	req.append(CRTREQ_GETNODEIP);
	req.append(" ");
	req.append(id_str.c_str());
	req.append("\r\n");

	cbr_debug("%s", req.c_str());

	s = cbr_sendall(crt_port, ip.c_str(), req.c_str(), strlen(req.c_str()));
	if (s < 0)
		goto out;

	cbr_debug(" wait for reply\n");

	// receive reply
	len = cbr_recv(s, tmp);
	if (len == -1)
		goto out;

	// NIS failed
	if (len == 0)
		goto out;

	converter.str(tmp);
	converter >> ip;

	cbr_debug(" id=%d ip: %s\n", id, ip.c_str());

	rc = 0;
out:
	delete[] tmp;

	return rc;
}

/**
 * Sends request for elementary concepts of the given Context Based Address.
 *
 * @param cba Context Based Address string
 * @param set concept set
 *
 * Sends CRTREQ_GETELEMENTARYCONCEPTS request to CRT server to obtain
 * elementary concepts of a given Context Based Address.
 */

void cbr_getElementaryConcepts(const char *cba, cbr_ConceptsSet &set)
{
	string req;

	// prepare request
	req.append(CRTREQ_GETELEMENTARYCONCEPTS);
	req.append(" ");
	req.append(cba);
	req.append("\r\n");

	getClasses(req, set);
}

/**
 * Sends request for super concepts of the given concept.
 *
 * @param concept concept
 * @param level level of depth to search for super concepts
 * @param set concept set
 *
 * Sends CRTREQ_GETSUPERCONCEPTS request to CRT server to obtain super
 * concepts of a given concept.
 */

void cbr_getSuperConcepts(const cbr_Concept &concept, int level,
			  cbr_ConceptsSet &set)
{
	string req;
	// integer to string
	stringstream oss;
	oss << level;
	string level_str(oss.str());

	if (concept.empty())
		return;

	// prepare request
	req.append(CRTREQ_GETSUPERCONCEPTS);
	req.append(" ");
	req.append(concept);
	req.append(" ");
	req.append(level_str);
	req.append("\r\n");

	getClasses(req, set);
}

/**
 * Sends request for sub concepts of the given concept.
 *
 * @param concept concept
 * @param set concept set
 *
 * Sends CRTREQ_GETSUBCONCEPTS request to CRT server to obtain sub
 * concepts of a given concept.
 */

void cbr_getSubConcepts(const cbr_Concept &concept, cbr_ConceptsSet &set)
{
	string req;

	// prepare request
	req.append(CRTREQ_GETSUBCONCEPTS);
	req.append(" ");
	req.append(concept);
	req.append("\r\n");

	getClasses(req, set);
}
